﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SyncUdp
{
    using SyncUdp.Interface;
    using SyncUdp.Model;
    using UnityEngine.UI;

    public class PlayerController : MonoBehaviour
    {
        public KeyCode sprintKeyboard = KeyCode.LeftShift;

        float _timeStep;
        float _floatThreshold;
        float _curSpeed;
        Vector3 _preLocalPos;
        Animator _anim;
        Camera _mainCamera;
        IPlayerModel _model;
        PlayerData _data;

        [SerializeField, Tooltip("显示玩家名字的画布")]
        Canvas _canvas;

        [SerializeField, Tooltip("显示玩家名字的背景")]
        Transform _playerNameBgTrans;

        [SerializeField, Tooltip("显示玩家名字的文本")]
        Text _playerNameTxt;

        public string PlayerName
        {
            get => transform.parent.name;
            set
            {
                transform.parent.name = value;
                _playerNameTxt.text = value;
            }
        }

        private void Awake()
        {
            _model = IOCContainer.GetInstance<IPlayerModel>();
            _data = _model.Data;
            _anim = GetComponent<Animator>();
            _mainCamera = Camera.main;
            _canvas.worldCamera = _mainCamera;
            var commonData = IOCContainer.GetInstance<IGameManager>().CommonData;
            _timeStep = commonData.serverDispatchesTime;
            _floatThreshold = 1 / commonData.unitsMultiplier;
            _preLocalPos = transform.localPosition;
        }

        private void FixedUpdate()
        {
            _playerNameBgTrans.forward = _mainCamera.transform.forward;
        }

        /// <summary>
        /// 接收玩家输入后存放在 Model 中
        /// </summary>
        public void ReceiveInputs()
        {
            var input = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
            bool isSprinting = Input.GetKey(sprintKeyboard) && input != Vector2.zero;
            // 发送输入后进行预测
            _model.SetInputs(isSprinting, input);
            UpdatePhysics(new UpdatePlayerPhysicsArgs()
            {
                isToPredict = true,
                isSprinting = _model.Inputs.isSprinting,
                movementInput = _model.Inputs.movementInput,
            });
        }

        public void UpdatePhysics(UpdatePlayerPhysicsArgs inputs)
        {
            var commonData = IOCContainer.GetInstance<IGameManager>().CommonData;
            float units = commonData.unitsMultiplier;
            bool falsePrediction = false;
            if (inputs.isToPredict)
            {
                // 计算本地输入和服务器发来的输入
                int d = System.Math.Abs(_model.Inputs.movementInput.sqrMagnitude - inputs.movementInput.sqrMagnitude);
                // 如果差异不在误差区间内（闭区间），则将位置重置
                if (d > (int)(System.Math.Pow(commonData.clientPredictionDifference, 2) * units))
                {
                    transform.localPosition = _preLocalPos;
                    falsePrediction = true;
                }
            }
            if (inputs.isToPredict == false || falsePrediction)
            {
                float x = inputs.movementInput.x / units;
                float y = inputs.movementInput.y / units;
                UpdateRotation(x);
                UpdatePosition(y, inputs.isSprinting);
            }
        }

        /// <summary>
        /// 更新人物旋转
        /// </summary>
        void UpdateRotation(float x)
        {
            // 这个条件其实是位移输入是否为 0 的判断
            if (Mathf.Abs(x) < _floatThreshold)
                return;
            float rotationY = x * _data.turnSpeed * _data.turnSpeedMultiplier;
            transform.rotation = Quaternion.Slerp(
                transform.rotation,
                Quaternion.Euler(0, transform.eulerAngles.y + rotationY, 0),
                _data.turnSpeedMultiplier * _timeStep
            );
        }

        void UpdatePosition(float y, bool isSprinting)
        {
            // 获取输入的值
            float speed = Mathf.Clamp(Mathf.Abs(y), 0f, 1f);
            // 根据输入的值计算最大的速率
            float maxSpeed = _data.walkSpeed;
            bool canSprint = isSprinting && y > 0;
            if (canSprint)
                maxSpeed *= _data.runSpeedMultiplier;
            // 平滑过度当前的速率
            _curSpeed = Mathf.Lerp(_curSpeed, maxSpeed * speed, 
                Mathf.Abs(y) >= _floatThreshold ? _timeStep : 5 * _timeStep);
            // 记录先前位置
            _preLocalPos = transform.localPosition;
            // 计算人物位置（固定 y 值为 0）
            transform.localPosition += (transform.forward * y) * _curSpeed * _timeStep;
            transform.localPosition = new Vector3(transform.localPosition.x, 0, transform.localPosition.z);
            // 播放动画，但不要通过动画位移！！！！
            _anim.SetFloat("Speed", _curSpeed / maxSpeed);
            _anim.SetFloat("Direction", Mathf.Min(y, 0));
            _anim.SetBool("IsSprinting", canSprint);
        }
    }

    public struct UpdatePlayerPhysicsArgs
    {
        public bool isToPredict;
        public bool isSprinting;
        public Vector2Int movementInput;
    }
}
